package com.swallow.auth.infrastructure.acl.oauth2.utils;

import com.swallow.auth.infrastructure.acl.oauth2.support.sms.SmsGrantAuthenticationProvider;
import com.swallow.auth.infrastructure.acl.oauth2.support.username.UPwdGrantAuthenticationProvider;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.Map;

/**
 * @author: yangjie.deng@resico.cn
 * @since: 2024-05-15 09:35:18
 * @version: v1.0.0
 * @describe: 通用工具类
 */
public class SecurityUtils {
    static final String ACCESS_TOKEN_REQUEST_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";


    public static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
        parameterMap.forEach((key, values) -> {
            for (String value : values) {
                parameters.add(key, value);
            }
        });
        return parameters;
    }

    public static void throwError(String errorCode, String parameterName) {
        OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, ACCESS_TOKEN_REQUEST_ERROR_URI);
        throw new OAuth2AuthenticationException(error);
    }

    /**
     *  判断当前登录是否为Oauth2请求 (一般是基于注册客户端的HttpBasic请求)
     *
     * @param authentication 请求对象
     * @return               判断结果
     */
    public static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
        OAuth2ClientAuthenticationToken clientPrincipal = null;
        if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
            clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
        }
        if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
            return clientPrincipal;
        }
        throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
    }


    /**
     *  给provider赋初值
     *
     * @param http            httpSecurity对象
     * @param smsProvider     短信验证码登录处理器
     * @param pwdProvider     用户名密码登录处理器
     */
    public static void initParamByProvider(HttpSecurity http, SmsGrantAuthenticationProvider smsProvider, UPwdGrantAuthenticationProvider pwdProvider) {
        // 从框架中获取provider中所需的bean
        OAuth2TokenGenerator<?> tokenGenerator = http.getSharedObject(OAuth2TokenGenerator.class);
        AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
        OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
        // 以上三个bean在build()方法之后调用是因为调用build方法时框架会尝试获取这些类，
        // 如果获取不到则初始化一个实例放入SharedObject中，所以要在build方法调用之后获取
        // 在通过set方法设置进provider中，但是如果在build方法之后调用authenticationProvider(provider)
        // 框架会提示unsupported_grant_type，因为已经初始化完了，在添加就不会生效了
        smsProvider.setTokenGenerator(tokenGenerator);
        smsProvider.setAuthorizationService(authorizationService);
        smsProvider.setAuthenticationManager(authenticationManager);

        pwdProvider.setTokenGenerator(tokenGenerator);
        pwdProvider.setAuthorizationService(authorizationService);
        pwdProvider.setAuthenticationManager(authenticationManager);
    }
}
